﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

using Noris.Tools.TraceVisualiser.Support;

namespace Noris.Tools.TraceVisualiser.Components
{
    #region ABSTRACT CLASS DataRequest : předek všech DataRequestů, implementuje IAsyncRequest, IDataSourceRequest. Obsahuje kromě jiného RequestType a TimeRange.
    /// <summary>
    /// Bázová třída pro požadavky do datového zdroje.
    /// Implementuje v sobě podporu pro AsyncWorker (typové přechodníky).
    /// </summary>
    internal abstract class DataRequest : IAsyncRequest, IDataSourceRequest
    {
        #region KONSTRUKCE, PŘECHODNÍKY
		public DataRequest(Action<IDataSourceRequest> runMethod, Action<IDataSourceRequest> doneMethod, DataSourceRequestType requestType, GraphDeclarationCls graphDeclaration, TimeRange timeRange, object userData)
        {
            this._Id = ++_LastId;
            this._RunMethod = runMethod;
            this._DoneMethod = doneMethod;
            this._Exception = null;
			this._State = AsyncRequestState.None;
            this._RequestType = requestType;
			this._GraphDeclaration = graphDeclaration;
			this._UserData = userData;
            this._TimeRange = timeRange;
        }
        private static int _LastId = 0;
		protected int _Id = 0;
        protected Action<IDataSourceRequest> _RunMethod;
		protected Action<IDataSourceRequest> _DoneMethod;
		protected Exception _Exception;
		protected AsyncRequestState _State;
		protected DataSourceRequestType _RequestType;
		protected GraphDeclarationCls _GraphDeclaration;
		protected object _UserData;
		protected TimeRange _TimeRange;
		protected Thread _OwnerThread;
        /// <summary>
        /// Přechodník RUN: když se AsyncWorker rozhodne, že na nás (my jsme požadavek) přišla řada, zavolá naši metodu, kterou mu vrátí delegát IAsyncRequest.RunMethod, což je this._AsyncRun.
        /// Předá jí request, což jsme my, ale typově object.
        /// My voláme metodu this._RunMethod, což je metoda (typicky v datovém zdroji), která byla předána v konstruktoru.
        /// </summary>
        /// <param name="data"></param>
        private void _AsyncRun(object data)
        {
            if (_RunMethod != null)
                _RunMethod(this);
        }
        /// <summary>
        /// Přechodník DONE: když je request zpracován, pak AsyncWorker vyvolá metodu, kterou mu vrátí delegát IAsyncRequest.DoneMethod, což je this._AsyncDone.
        /// Předá jí request (nyní je již naplněn výsledky), což jsme de facto stále my (this), ale typově object.
        /// My voláme metodu this._DoneMethod, což je metoda (typicky v grafu), která byla předána v konstruktoru.
        /// </summary>
        /// <param name="data"></param>
        private void _AsyncDone(object data)
        {
            if (_DoneMethod != null)
                _DoneMethod(this);
        }
		public override string ToString()
		{
			return "Request: " + _RequestType.ToString() + "; ID=" + this._Id.ToString() + "; State = " + this._State.ToString();
		}
        #endregion
		#region IAsyncRequest Members : je implementován explicitně, aby nebyl příliš viditelný v rámci Requestů.    IDisposable.Dispose()
		Action<object> IAsyncRequest.RunMethod { get { return _AsyncRun; } }
        Action<object> IAsyncRequest.DoneMethod { get { return _AsyncDone; } set { _DoneMethod = value; } }   // Pozor: get vrací zdejší metodu _AsyncDone(), která vždy vyvolá metodu _DoneMethod(this). Set vloží nový cíl do delegáta this._DoneMethod, čímž změní cíl volání.
        Exception IAsyncRequest.Exc { get { return _Exception; } set { _Exception = value; } }
		AsyncRequestState IAsyncRequest.State { get { return _State; } set { _State = value; } }
		Thread IAsyncRequest.OwnerThread { get { return _OwnerThread; } set { _OwnerThread = value; } }
		void IDisposable.Dispose()
		{
			this.Dispose();                    // Háček pro potomstvo volám před core Dispose():
			_RunMethod = null;
			_DoneMethod = null;
			_Exception = null;
			_State = AsyncRequestState.Disposed;
			_OwnerThread = null;
		}
        #endregion
        #region IDataSourceRequest Members : je implementován implicitně, protože se s ním pracuje jak v IDataSourceRequest, tak v potomcích třídy DataRequest.
		/// <summary>
		/// Požadavek: Typ požadavku
		/// </summary>
		public DataSourceRequestType RequestType { get { return this._RequestType; } protected set { this._RequestType = value; } }
        /// <summary>
        /// Pokud běh požadavku skončil s chybou, pak je uložena zde.
        /// </summary>
        public Exception ResultException { get { return _Exception; } }
        #endregion
        #region RUN REQUEST
		/// <summary>
		/// Zajistí zařazení tohoto requestu do fronty asynchronních úkolů
		/// </summary>
        public void AddRequestAsync()
        {
			AsyncWorker.AddRequestAsync(this);
		}
		/// <summary>
		/// Zajistí zařazení tohoto requestu na začátek fronty asynchronních úkolů, a počká na jeho dokončení.
		/// Z této metody se řízení vrací až po zpracování requestu.
		/// To, zda se po doběhnutí requestu bude volat metoda Done, lze potlačit zadáním parametru callDone = false.
        /// <para>
        /// Zadá nový požadavek pro synchronní zpracování. 
        /// Bude zpracován okamžitě poté, kdy doběhne zpracování právě běžícího požadavku.
        /// Toto vlákno počká, až bude zdejší požadavek dokončen, a řízení vrátí hned poté (takže data v requestu budou naplněna výsledky funkce).
        /// Tento požadavek nemusí mít naplněnu metodu Done, protože výsledek dostane volající kód - ve svém threadu.
        /// Pokud bude metoda Done naplněna, zavolá se, ale v threadu Async.
        /// Tím, že se požadavek vykoná touto cestou se zajistí to, že výkonný objekt (metoda Run) bude dostávat veškeré requesty v jediném threadu,
        /// a nedojde v něm tak k multithreadingu (se všemi problémy se zabezpečením).
        /// </para>
        /// <para>
        /// POZOR: tento režim má za úkol po dokončení výkonné metody nechat request naplněný daty, volající si je převezme.
        /// Ale protože request je IDisposable, je třeba jej použít v using patternu, anebo po jeho zpracování dát ((IDisposable)request).Dispose() !!!
        /// </para>
        /// </summary>
        /// <param name="callDone">Volba, zda se po dokončení má volat metoda Done: 
		/// true = volat, false = nevolat (výsledky se převezmou z requestu ve volající metodě</param>
		public void RunRequestSync(bool callDone)
		{
			if (!callDone)
				this._DoneMethod = null;
			AsyncWorker.RunRequestSync(this);
		}
        #endregion
        #region ZÁKLADNÍ PROPERTY, KTERÉ VYUŽIJÍ TÉMĚŘ VŠECHNY ZDĚDĚNÉ TŘÍDY
        /// <summary>
        /// ID tohoto requestu. Nepřerušovaná unikátní řada, počínaje od spuštění aplikace.
        /// </summary>
        public int RequestId { get { return _Id; } }
		/// <summary>
		/// Dočasný GID odvozený od čísla tohoto requestu. Používá se pro identifikaci temporary elementu.
		/// </summary>
		public GID RequestGId { get { return new GID(-1, this.RequestId); } }
		/// <summary>
		/// Deklarace grafu, který má tento požadavek.
		/// </summary>
		public GraphDeclarationCls GraphDeclaration { get { return _GraphDeclaration; } }
		/// <summary>
        /// Časové rozmezí dotazu.
        /// </summary>
        public TimeRange TimeRange { get { return this._TimeRange; } }
		/// <summary>
		/// Libovolná data pro uživatele
		/// </summary>
		public object UserData { get { return this._UserData; } set { this._UserData = value; } }
        /// <summary>
        /// True, pokud běh požadavku již skončil a nedošlo v něm k chybě.
        /// </summary>
        public bool DoneOK { get { return (this._Exception == null && this._State == AsyncRequestState.Done); } }
		/// <summary>
		/// Dispose háček pro potomstvo.
		/// Nemusí volat base.Dispose().
		/// </summary>
		protected virtual void Dispose()
		{
		}
        #endregion
    }
    #endregion
	#region CLASS DataRequestRows : abstraktní požadavek, který navíc k základnímu requestu přidává property DataRows.
	/// <summary>
	/// DataRequestRows : abstraktní požadavek, který navíc k základnímu requestu přidává property DataRows.
	/// </summary>
	internal abstract class DataRequestRows : DataRequest
	{
		public DataRequestRows(Action<IDataSourceRequest> runMethod, Action<IDataSourceRequest> doneMethod, DataSourceRequestType requestType, GraphDeclarationCls graphDeclaration, TimeRange timeRange, object userData)
			: base(runMethod, doneMethod, requestType, graphDeclaration, timeRange, userData)
		{ }
		/// <summary>
		/// Výsledek: načtené řádky. Pokud budou načítány v jednom chodu jak řádky Parent, tak SubNodes, 
		/// v takovém případě musí být řádky Parent uvedeny v seznamu dříve než jejich SubNodes.
		/// </summary>
		public List<IDataRow> DataRows { get; set; }
		/// <summary>
		/// Seznam řádků, jejichž některé elementy byly změněny, a je třeba zahodit elementy načtené v datové vrstvě a znovu je načíst z datové vrstvy
		/// </summary>
		public List<GID> InvalidateContentRows { get; set; }
	}
	#endregion
	#region CLASS DataRequestReadRows : požadavek na načtení řádků (všech / podřízených jednomu řádku (SubNodes))
	/// <summary>
	/// DataRequestReadRows : požadavek na načtení řádků (všech / podřízených jednomu řádku (SubNodes))
	/// </summary>
	internal class DataRequestReadRows : DataRequestRows
    {
		public DataRequestReadRows(Action<IDataSourceRequest> runMethod, Action<IDataSourceRequest> doneMethod, GraphDeclarationCls graphDeclaration, TimeRange timeRange, object userData,
			GID graphRowGid, GID parentRowGid, GID refreshRowGId)
            : base(runMethod, doneMethod, DataSourceRequestType.ReadRows, graphDeclaration, timeRange, userData)
        {
			this.GraphRowGid = graphRowGid;
            this.ParentRowGid = parentRowGid;
			this.RefreshRowGId = refreshRowGId;
        }
		/// <summary>
		/// Požadavek: GID grafu (obsahuje třídu dat, která je deklarována v deklaraci grafu).
		/// </summary>
		public GID GraphRowGid { get; private set; }
		/// <summary>
		/// Požadavek: GID parent řádku. Pokud není Empty, pak se mají načíst řádky podřízené danému řádku (SubNodes).
		/// </summary>
		public GID ParentRowGid { get; private set; }
		/// <summary>
		/// Požadavek: GID řádku, který se má načíst. Použije se při refreshi řádku.
		/// </summary>
		public GID RefreshRowGId { get; private set; }
    }
	#endregion
	#region CLASS DataRequestReadElementsForRows : požadavek na načtení elementů jednoho řádku, za dané období
	/// <summary>
	/// DataRequestReadElementsForRows : požadavek na načtení elementů jednoho řádku, za dané období
	/// </summary>
	internal class DataRequestReadElementsForRows : DataRequestRows
    {
		public DataRequestReadElementsForRows(Action<IDataSourceRequest> runMethod, Action<IDataSourceRequest> doneMethod, GraphDeclarationCls graphDeclaration, TimeRange timeRange, object userData,
			GID rowGid)
            : base(runMethod, doneMethod, DataSourceRequestType.ReadElementsForRows, graphDeclaration, timeRange, userData)
        {
            this.RowGid = rowGid;
        }
        public GID RowGid { get; private set; }
        /// <summary>
        /// Data načtených elementů.
        /// Protože element v sobě neobsahuje GID řádku, a protože odpověď může obsahovat elementy z více řádků než z jednoho,
        /// proto je seznam uspořádán jako seznam prvků KeyValuePair, kde Key je GID řádku a Value jsou data elementu.
        /// </summary>
        public List<KeyValuePair<GID, IDataElement>> DataRowElements { get; set; }
        /// <summary>
        /// Příznak, že jsou načteny všechny elementy dotyčného řádku, a více jich již mít nebude.
        /// Graf si to zapamatuje, a nebude po datovém zdroji žádat další načítání elementů pro tento řádek.
        /// </summary>
        public bool AllElementsLoaded { get; set; }
    }
	#endregion
    #region CLASS DataRequestReadSpecific : požadavek na specifické načítání dat ve volném čase AsyncWork, načítá data jednoho souboru
    /// <summary>
    /// DataRequestReadSpecific : požadavek na specifické načítání dat ve volném čase AsyncWork, načítá data jednoho souboru
    /// </summary>
    internal class DataRequestReadSpecific : DataRequestRows
    {
        public DataRequestReadSpecific(Action<IDataSourceRequest> runMethod, Action<IDataSourceRequest> doneMethod, GraphDeclarationCls graphDeclaration, TimeRange timeRange, object userData,
            GID traceFileGId)
            : base(runMethod, doneMethod, DataSourceRequestType.ReadSpecific, graphDeclaration, timeRange, userData)
        {
            this.TraceFileGId = traceFileGId;
        }
        public GID TraceFileGId { get; private set; }
    }
    #endregion
	#region CLASS DataRequestPointerAccept : požadavek akceptování pointeru (Expand nodes, First Row)
	/// <summary>
	/// DataRequestPointerAccept : požadavek akceptování pointeru (Expand nodes, First Row)
    /// </summary>
    internal class DataRequestPointerAccept : DataRequest
    {
        public DataRequestPointerAccept(Action<IDataSourceRequest> runMethod, IGraphPointer graphPointer)
            : base(runMethod, null, DataSourceRequestType.None, null, TimeRange.Empty, null)
        {
			this.GraphPointer = graphPointer;
        }
        public DataRequestPointerAccept(DataRequestPointerAccept originalRequest)
            : base(originalRequest._RunMethod, null, DataSourceRequestType.None, null, TimeRange.Empty, null)
        {
			this.GraphPointer = originalRequest.GraphPointer;
        }
		public IGraphPointer GraphPointer { get; private set; }
    }
    #endregion
    #region CLASS DataRequestInitialize : požadavek na inicializaci dat
	/// <summary>
	/// DataRequestInitialize : požadavek na inicializaci dat
	/// </summary>
	internal class DataRequestInitialize : DataRequest
	{
		/// <summary>
		/// Vytvoří objekt typu DataRequestInitialize : požadavek na inicializaci dat
		/// </summary>
		/// <param name="runMethod">Metoda spuštěná jak Run = výkonná metoda (v asynchronním vláknu)</param>
		/// <param name="doneMethod">Metoda spouštěná po doběhnutí Run (v asynchronním vláknu)</param>
		/// <param name="gd"></param>
		/// <param name="timeRange"></param>
		/// <param name="userData"></param>
		public DataRequestInitialize(Action<IDataSourceRequest> runMethod, Action<IDataSourceRequest> doneMethod, GraphDeclarationCls gd, TimeRange timeRange, object userData)
			: base(runMethod, doneMethod, DataSourceRequestType.Initialize, gd, timeRange, userData)
		{
			this.UserData = userData;
		}
        /// <summary>
        /// Požadavek: poté, co bude určen časový rozsah InitialTimeRange, zajistit načtení odpovídajících trace souborů rovnou při inicializaci
        /// </summary>
        public bool LoadInitialTraces { get; set; }
        /// <summary>
        /// Výstup: Požadavek, aby byl zavolán request ReadRows (ReadFirstDataList())
        /// </summary>
		public bool ReloadAllRows { get; set; }
        /// <summary>
        /// Výstup: Řádky, které by se měly načíst do grafu
        /// </summary>
		public List<IDataRow> DataRows { get; set; }
        /// <summary>
        /// Výstup: Časový interval, který by měla zobrazit časová osa
        /// </summary>
        public TimeRange InitialTimeRange { get; set; }
	}
	#endregion
	#region CLASS DataRequestFunction : požadavek na spuštění funkce
	/// <summary>
	/// DataRequestFunction : požadavek na spuštění funkce
	/// </summary>
	internal class DataRequestFunction : DataRequestInitialize
	{
		public DataRequestFunction(Action<IDataSourceRequest> runMethod, Action<IDataSourceRequest> doneMethod, GraphDeclarationCls graphDeclaration, TimeRange timeRange, object userData,
			IComparable functionID)
			: base(runMethod, doneMethod, graphDeclaration, timeRange, userData)
		{
			this.RequestType = DataSourceRequestType.RunFunction;
			this.FunctionID = functionID;
		}
		public IComparable FunctionID { get; private set; }
	}
	#endregion
}
